/*
 * Copyright (C) 2011 Keijiro Takahashi
 * Copyright (C) 2012 GREE, Inc.
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
using System.IO;
using System.Text.RegularExpressions;
#endif
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using Callback = System.Action<string>;

#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
public class UnitySendMessageDispatcher {
    public static void Dispatch(string name, string method, string message) {
        GameObject obj = GameObject.Find(name);
        if (obj != null) {
            obj.SendMessage(method, message);
        }
    }
}
#endif

public class WebViewObject : MonoBehaviour {
    Callback onJS;
    Callback onError;
    Callback onHttpError;
    Callback onStarted;
    Callback onLoaded;
    bool visibility;
    int mMarginLeft;
    int mMarginTop;
    int mMarginRight;
    int mMarginBottom;
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
    IntPtr webView;
    Rect rect;
    Texture2D texture;
    string inputString;
    bool hasFocus;
#elif UNITY_IPHONE
    IntPtr webView;
#elif UNITY_ANDROID
    AndroidJavaObject webView;
    
    bool mVisibility;
    bool mIsKeyboardVisible0;
    bool mIsKeyboardVisible;
    float mResumedTimestamp;
    
    void OnApplicationPause(bool paused)
    {
        if (webView == null)
            return;
        if (!paused)
        {
            webView.Call("SetVisibility", false);
            mResumedTimestamp = Time.realtimeSinceStartup;
        }
        webView.Call("OnApplicationPause", paused);
    }

    void Update()
    {
        if (webView == null)
            return;
        if (mResumedTimestamp != 0.0f && Time.realtimeSinceStartup - mResumedTimestamp > 0.5f)
        {
            mResumedTimestamp = 0.0f;
            webView.Call("SetVisibility", mVisibility);
        }
    }

    /// Called from Java native plugin to set when the keyboard is opened
    public void SetKeyboardVisible(string pIsVisible)
    {
        mIsKeyboardVisible = (pIsVisible == "true");
        if (mIsKeyboardVisible != mIsKeyboardVisible0)
        {
            mIsKeyboardVisible0 = mIsKeyboardVisible;
            SetMargins(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom);
        }
    }
    
    public int AdjustBottomMargin(int bottom)
    {
        if (!mIsKeyboardVisible)
        {
            return bottom;
        }
        else
        {
            int keyboardHeight = 0;
            using(AndroidJavaClass UnityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            {
                AndroidJavaObject View =
 UnityClass.GetStatic<AndroidJavaObject>("currentActivity").Get<AndroidJavaObject>("mUnityPlayer").Call<AndroidJavaObject>("getView");
                using(AndroidJavaObject Rct = new AndroidJavaObject("android.graphics.Rect"))
                {
                    View.Call("getWindowVisibleDisplayFrame", Rct);
                    keyboardHeight = View.Call<int>("getHeight") - Rct.Call<int>("height");
                }
            }
            return (bottom > keyboardHeight) ? bottom : keyboardHeight;
        }
    }
#else
    IntPtr webView;
#endif

    public bool IsKeyboardVisible {
        get {
#if !UNITY_EDITOR && UNITY_ANDROID
            return mIsKeyboardVisible;
#elif !UNITY_EDITOR && UNITY_IPHONE
            return TouchScreenKeyboard.visible;
#else
            return false;
#endif
        }
    }

#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
#if WEBVIEW_SEPARATED
    [DllImport("WebViewSeparated")]
    private static extern string _CWebViewPlugin_GetAppPath();
    [DllImport("WebViewSeparated")]
    private static extern IntPtr _CWebViewPlugin_Init(
        string gameObject, bool transparent, int width, int height, string ua, bool ineditor);
    [DllImport("WebViewSeparated")]
    private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_SetRect(
        IntPtr instance, int width, int height);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_SetVisibility(
        IntPtr instance, bool visibility);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_LoadURL(
        IntPtr instance, string url);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_LoadHTML(
        IntPtr instance, string html, string baseUrl);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_EvaluateJS(
        IntPtr instance, string url);
    [DllImport("WebViewSeparated")]
    private static extern int _CWebViewPlugin_Progress(
        IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern bool _CWebViewPlugin_CanGoBack(
        IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern bool _CWebViewPlugin_CanGoForward(
        IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_GoBack(
        IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_GoForward(
        IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_Update(IntPtr instance,
        int x, int y, float deltaY, bool down, bool press, bool release,
        bool keyPress, short keyCode, string keyChars,
        bool refreshBitmap);
    [DllImport("WebViewSeparated")]
    private static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_SetTextureId(IntPtr instance, int textureId);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_SetCurrentInstance(IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern IntPtr GetRenderEventFunc();
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
    [DllImport("WebViewSeparated")]
    private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
    [DllImport("WebViewSeparated")]
    private static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
    [DllImport("WebViewSeparated")]
    private static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
#else
    [DllImport("WebView")]
    static extern string _CWebViewPlugin_GetAppPath();

    [DllImport("WebView")]
    static extern IntPtr _CWebViewPlugin_Init(
        string gameObject, bool transparent, int width, int height, string ua, bool ineditor);

    [DllImport("WebView")]
    static extern int _CWebViewPlugin_Destroy(IntPtr instance);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_SetRect(
        IntPtr instance, int width, int height);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_SetVisibility(
        IntPtr instance, bool visibility);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_LoadURL(
        IntPtr instance, string url);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_LoadHTML(
        IntPtr instance, string html, string baseUrl);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_EvaluateJS(
        IntPtr instance, string url);

    [DllImport("WebView")]
    static extern int _CWebViewPlugin_Progress(
        IntPtr instance);

    [DllImport("WebView")]
    static extern bool _CWebViewPlugin_CanGoBack(
        IntPtr instance);

    [DllImport("WebView")]
    static extern bool _CWebViewPlugin_CanGoForward(
        IntPtr instance);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_GoBack(
        IntPtr instance);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_GoForward(
        IntPtr instance);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_Update(IntPtr instance,
        int x, int y, float deltaY, bool down, bool press, bool release,
        bool keyPress, short keyCode, string keyChars,
        bool refreshBitmap);

    [DllImport("WebView")]
    static extern int _CWebViewPlugin_BitmapWidth(IntPtr instance);

    [DllImport("WebView")]
    static extern int _CWebViewPlugin_BitmapHeight(IntPtr instance);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_SetTextureId(IntPtr instance, int textureId);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_SetCurrentInstance(IntPtr instance);

    [DllImport("WebView")]
    static extern IntPtr GetRenderEventFunc();

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);

    [DllImport("WebView")]
    static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);

    [DllImport("WebView")]
    static extern void _CWebViewPlugin_ClearCustomHeader(IntPtr instance);

    [DllImport("WebView")]
    static extern string _CWebViewPlugin_GetMessage(IntPtr instance);
#endif
#elif UNITY_IPHONE
    [DllImport("__Internal")]
    private static extern IntPtr _CWebViewPlugin_Init(string gameObject, bool transparent, string ua, bool enableWKWebView);
    [DllImport("__Internal")]
    private static extern int _CWebViewPlugin_Destroy(IntPtr instance);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_SetMargins(
        IntPtr instance, int left, int top, int right, int bottom);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_SetVisibility(
        IntPtr instance, bool visibility);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_LoadURL(
        IntPtr instance, string url);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_LoadHTML(
        IntPtr instance, string html, string baseUrl);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_EvaluateJS(
        IntPtr instance, string url);
    [DllImport("__Internal")]
    private static extern int _CWebViewPlugin_Progress(
        IntPtr instance);
    [DllImport("__Internal")]
    private static extern bool _CWebViewPlugin_CanGoBack(
        IntPtr instance);
    [DllImport("__Internal")]
    private static extern bool _CWebViewPlugin_CanGoForward(
        IntPtr instance);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_GoBack(
        IntPtr instance);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_GoForward(
        IntPtr instance);
    [DllImport("__Internal")]
    private static extern void _CWebViewPlugin_SetFrame(
        IntPtr instance, int x , int y , int width , int height);
    [DllImport("__Internal")]
    private static extern void   _CWebViewPlugin_AddCustomHeader(IntPtr instance, string headerKey, string headerValue);
    [DllImport("__Internal")]
    private static extern string _CWebViewPlugin_GetCustomHeaderValue(IntPtr instance, string headerKey);
    [DllImport("__Internal")]
    private static extern void   _CWebViewPlugin_RemoveCustomHeader(IntPtr instance, string headerKey);
    [DllImport("__Internal")]
    private static extern void   _CWebViewPlugin_ClearCustomHeader(IntPtr instance);
    [DllImport("__Internal")]
    private static extern void   _CWebViewPlugin_ClearCookies();
#endif

    public static bool IsWebViewAvailable() {
#if !UNITY_EDITOR && UNITY_ANDROID
        return (new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin")).CallStatic<bool>("IsWebViewAvailable");
#else
        return true;
#endif
    }

    public void Init(
        Callback cb = null,
        bool transparent = false,
        string ua = "",
        Callback err = null,
        Callback httpErr = null,
        Callback ld = null,
        bool enableWKWebView = false,
        Callback started = null) {
        this.onJS = cb;
        this.onError = err;
        this.onHttpError = httpErr;
        this.onStarted = started;
        this.onLoaded = ld;
#if UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.init", name);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
        Debug.LogError("Webview is not supported on this platform.");
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        {
            var uri = new Uri(_CWebViewPlugin_GetAppPath());
            var info = File.ReadAllText(uri.LocalPath + "Contents/Info.plist");
            if (Regex.IsMatch(info, @"<key>CFBundleGetInfoString</key>\s*<string>Unity version [5-9]\.[3-9]")
                && !Regex.IsMatch(info,
                    @"<key>NSAppTransportSecurity</key>\s*<dict>\s*<key>NSAllowsArbitraryLoads</key>\s*<true/>\s*</dict>")
            ) {
                Debug.LogWarning(
                    "<color=yellow>WebViewObject: NSAppTransportSecurity isn't configured to allow HTTP. If you need to allow any HTTP access, please shutdown Unity and invoke:</color>\n/usr/libexec/PlistBuddy -c \"Add NSAppTransportSecurity:NSAllowsArbitraryLoads bool true\" /Applications/Unity/Unity.app/Contents/Info.plist");
            }
        }
#if UNITY_EDITOR_OSX
        // if (string.IsNullOrEmpty(ua)) {
        //     ua = @"Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53";
        // }
#endif
        this.webView = _CWebViewPlugin_Init(this.name,
            transparent,
            Screen.width,
            Screen.height,
            ua,
            Application.platform == RuntimePlatform.OSXEditor);
        // define pseudo requestAnimationFrame.
        this.EvaluateJS(@"(function() {
            var vsync = 1000 / 60;
            var t0 = window.performance.now();
            window.requestAnimationFrame = function(callback, element) {
                var t1 = window.performance.now();
                var duration = t1 - t0;
                var d = vsync - ((duration > vsync) ? duration % vsync : duration);
                var id = window.setTimeout(function() {t0 = window.performance.now(); callback(t1 + d);}, d);
                return id;
            };
        })()");
        this.rect = new Rect(0, 0, Screen.width, Screen.height);
        this.OnApplicationFocus(true);
#elif UNITY_IPHONE
        webView = _CWebViewPlugin_Init(name, transparent, ua, enableWKWebView);
#elif UNITY_ANDROID
        webView = new AndroidJavaObject("net.gree.unitywebview.CWebViewPlugin");
        webView.Call("Init", name, transparent, ua);
#else
        Debug.LogError("Webview is not supported on this platform.");
#endif
    }

    protected virtual void OnDestroy() {
#if UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.destroy", name);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_Destroy(this.webView);
        this.webView = IntPtr.Zero;
        Destroy(this.texture);
#elif UNITY_IPHONE
        if (webView == IntPtr.Zero)
            return;
        _CWebViewPlugin_Destroy(webView);
        webView = IntPtr.Zero;
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("Destroy");
        webView = null;
#endif
    }

    /** Use this function instead of SetMargins to easily set up a centered window */
    public void SetCenterPositionWithScale(Vector2 center, Vector2 scale) {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        this.rect.x = center.x + (Screen.width - scale.x) / 2;
        this.rect.y = center.y + (Screen.height - scale.y) / 2;
        this.rect.width = scale.x;
        this.rect.height = scale.y;
#elif UNITY_IPHONE
        if (webView == IntPtr.Zero) return;
        _CWebViewPlugin_SetFrame(webView,(int)center.x,(int)center.y,(int)scale.x,(int)scale.y);
#endif
    }

    public void SetMargins(int left, int top, int right, int bottom) {
#if UNITY_WEBPLAYER
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        if (this.webView == IntPtr.Zero) {
            return;
        }
#elif UNITY_IPHONE
        if (webView == IntPtr.Zero)
            return;
#elif UNITY_ANDROID
        if (webView == null)
            return;
#endif
        this.mMarginLeft = left;
        this.mMarginTop = top;
        this.mMarginRight = right;
        this.mMarginBottom = bottom;
#if UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.setMargins", name, left, top, right, bottom);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        int width = Screen.width - (left + right);
        int height = Screen.height - (bottom + top);
        _CWebViewPlugin_SetRect(this.webView, width, height);
        this.rect = new Rect(left, bottom, width, height);
#elif UNITY_IPHONE
        _CWebViewPlugin_SetMargins(webView, left, top, right, bottom);
#elif UNITY_ANDROID
        webView.Call("SetMargins", left, top, right, AdjustBottomMargin(bottom));
#endif
    }

    public void SetVisibility(bool v) {
#if UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.setVisibility", name, v);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_SetVisibility(this.webView, v);
#elif UNITY_IPHONE
        if (webView == IntPtr.Zero)
            return;
        _CWebViewPlugin_SetVisibility(webView, v);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        mVisibility = v;
        webView.Call("SetVisibility", v);
#endif
        this.visibility = v;
    }

    public bool GetVisibility() {
        return this.visibility;
    }

    public void LoadURL(string url) {
        if (string.IsNullOrEmpty(url)) {
            return;
        }
#if UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.loadURL", name, url);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_LoadURL(this.webView, url);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("LoadURL", url);
#endif
    }

    public void LoadHTML(string html, string baseUrl) {
        if (string.IsNullOrEmpty(html)) {
            return;
        }

        if (string.IsNullOrEmpty(baseUrl)) {
            baseUrl = "";
        }
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_LoadHTML(this.webView, html, baseUrl);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("LoadHTML", html, baseUrl);
#endif
    }

    public void EvaluateJS(string js) {
#if UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.evaluateJS", name, js);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_EvaluateJS(this.webView, js);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("EvaluateJS", js);
#endif
    }

    public int Progress() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
        return 0;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
        return 0;
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return 0;
        }

        return _CWebViewPlugin_Progress(this.webView);
#elif UNITY_ANDROID
        if (webView == null)
            return 0;
        return webView.Get<int>("progress");
#endif
    }

    public bool CanGoBack() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
        return false;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
        return false;
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return false;
        }

        return _CWebViewPlugin_CanGoBack(this.webView);
#elif UNITY_ANDROID
        if (webView == null)
            return false;
        return webView.Get<bool>("canGoBack");
#endif
    }

    public bool CanGoForward() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
        return false;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
        return false;
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return false;
        }

        return _CWebViewPlugin_CanGoForward(this.webView);
#elif UNITY_ANDROID
        if (webView == null)
            return false;
        return webView.Get<bool>("canGoForward");
#endif
    }

    public void GoBack() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_GoBack(this.webView);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("GoBack");
#endif
    }

    public void GoForward() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_GoForward(this.webView);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("GoForward");
#endif
    }

    public void CallOnError(string error) {
        if (this.onError != null) {
            this.onError(error);
        }
    }

    public void CallOnHttpError(string error) {
        if (this.onHttpError != null) {
            this.onHttpError(error);
        }
    }

    public void CallOnStarted(string url) {
        if (this.onStarted != null) {
            this.onStarted(url);
        }
    }

    public void CallOnLoaded(string url) {
        if (this.onLoaded != null) {
            this.onLoaded(url);
        }
    }

    public void CallFromJS(string message) {
        if (this.onJS != null) {
#if !UNITY_ANDROID
            message = WWW.UnEscapeURL(message);
#endif
            this.onJS(message);
        }
    }


    public void AddCustomHeader(string headerKey, string headerValue) {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_AddCustomHeader(this.webView, headerKey, headerValue);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("AddCustomHeader", headerKey, headerValue);
#endif
    }

    public string GetCustomHeaderValue(string headerKey) {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
        return null;
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
        return null;
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return null;
        }

        return _CWebViewPlugin_GetCustomHeaderValue(this.webView, headerKey);
#elif UNITY_ANDROID
        if (webView == null)
            return null;
        return webView.Call<string>("GetCustomHeaderValue", headerKey);
#endif
    }

    public void RemoveCustomHeader(string headerKey) {
#if UNITY_WEBPLAYER
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_RemoveCustomHeader(this.webView, headerKey);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("RemoveCustomHeader", headerKey);
#endif
    }

    public void ClearCustomHeader() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (this.webView == IntPtr.Zero) {
            return;
        }

        _CWebViewPlugin_ClearCustomHeader(this.webView);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("ClearCustomHeader");
#endif
    }

    public void ClearCookies() {
#if UNITY_WEBPLAYER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        //TODO: UNSUPPORTED
#elif UNITY_IPHONE && !UNITY_EDITOR
        if (webView == IntPtr.Zero)
            return;

        _CWebViewPlugin_ClearCookies();
#elif UNITY_ANDROID && !UNITY_EDITOR
        if (webView == null)
            return;
        webView.Call("ClearCookies");
#endif
    }


#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
    void OnApplicationFocus(bool focus) {
        this.hasFocus = focus;
    }

    void Update() {
        if (this.hasFocus) {
            this.inputString += Input.inputString;
        }

        for (;;) {
            if (this.webView == IntPtr.Zero) {
                break;
            }

            string s = _CWebViewPlugin_GetMessage(this.webView);
            if (s == null) {
                break;
            }

            switch (s[0]) {
                case 'E':
                    this.CallOnError(s.Substring(1));
                    break;
                case 'S':
                    this.CallOnStarted(s.Substring(1));
                    break;
                case 'L':
                    this.CallOnLoaded(s.Substring(1));
                    break;
                case 'J':
                    this.CallFromJS(s.Substring(1));
                    break;
            }
        }
    }

    public int bitmapRefreshCycle = 1;

    void OnGUI() {
        if (this.webView == IntPtr.Zero || !this.visibility) {
            return;
        }

        Vector3 pos = Input.mousePosition;
        bool down = Input.GetButton("Fire1");
        bool press = Input.GetButtonDown("Fire1");
        bool release = Input.GetButtonUp("Fire1");
        float deltaY = Input.GetAxis("Mouse ScrollWheel");
        bool keyPress = false;
        string keyChars = "";
        short keyCode = 0;
        if (this.inputString != null && this.inputString.Length > 0) {
            keyPress = true;
            keyChars = this.inputString.Substring(0, 1);
            keyCode = (short) this.inputString[0];
            this.inputString = this.inputString.Substring(1);
        }

        bool refreshBitmap = (Time.frameCount % this.bitmapRefreshCycle == 0);
        _CWebViewPlugin_Update(this.webView,
            (int) (pos.x - this.rect.x), (int) (pos.y - this.rect.y), deltaY,
            down, press, release, keyPress, keyCode, keyChars,
            refreshBitmap);
        if (refreshBitmap) {
            {
                var w = _CWebViewPlugin_BitmapWidth(this.webView);
                var h = _CWebViewPlugin_BitmapHeight(this.webView);
                if (this.texture == null || this.texture.width != w || this.texture.height != h) {
                    this.texture = new Texture2D(w, h, TextureFormat.RGBA32, false, true);
                    this.texture.filterMode = FilterMode.Bilinear;
                    this.texture.wrapMode = TextureWrapMode.Clamp;
                }
            }
            _CWebViewPlugin_SetTextureId(this.webView, (int) this.texture.GetNativeTexturePtr());
            _CWebViewPlugin_SetCurrentInstance(this.webView);
#if UNITY_4_6 || UNITY_5_0 || UNITY_5_1
            GL.IssuePluginEvent(-1);
#else
            GL.IssuePluginEvent(GetRenderEventFunc(), -1);
#endif
        }

        if (this.texture != null) {
            Matrix4x4 m = GUI.matrix;
            GUI.matrix
                = Matrix4x4.TRS(
                    new Vector3(0, Screen.height, 0),
                    Quaternion.identity,
                    new Vector3(1, -1, 1));
            GUI.DrawTexture(this.rect, this.texture);
            GUI.matrix = m;
        }
    }
#endif
}